package com.nulldev.util.networking.async2.udp;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

import com.nulldev.util.JVM.JVM;

public final class UDPListener {
	private Async2UDPServer fUDPServer;
	private String fName;
	private InetAddress fIPAddress;
	private int fPort;
	private int fReadBufferSize;
	private int fReadDataTimeOut;
	public DatagramSocket fSocket;
	private boolean fActive = false;
	private Thread fReadIncomingDataThread;
	public long fBytesIn = 0, fBytesOut = 0;

	public IncomingUDPServerPacket fLastIncomingPacket = null;
	public OutgoingUDPServerPacket fLastOutgoingPacket = null;

	public UDPListener(final Async2UDPServer UDPServer, final String name, final InetAddress ipAddress, final int port, final int readBufferSize,
			final int readDataTimeOut) {
		this.fUDPServer = UDPServer;
		this.fName = name;
		this.fIPAddress = ipAddress;
		this.fPort = port;
		this.fReadBufferSize = readBufferSize;
		this.fReadDataTimeOut = readDataTimeOut;
	}

	public void stop() {
		if (this.fActive) {
			this.fActive = false;
			try {
				this.fSocket.close();
			} catch (Exception e) {
				JVM.errorln(e);
			}

			if (this.fLastIncomingPacket != null) {
				this.fLastIncomingPacket.cancel();
			}

			if (this.fLastOutgoingPacket != null) {
				this.fLastOutgoingPacket.cancel();
			}

			try {
				this.fReadIncomingDataThread.interrupt();
			} catch (Exception e) {
				JVM.errorln(e);
			}
		}
	}

	public void start() throws SocketException {
		this.stop();
		try {
			this.fSocket = new DatagramSocket(this.fPort, this.fIPAddress);
			this.fSocket.setReceiveBufferSize(this.fReadBufferSize);
			this.fSocket.setSendBufferSize(this.fReadBufferSize);

			if (this.fReadDataTimeOut > 0) {
				this.fSocket.setSoTimeout(this.fReadDataTimeOut);
			}

			this.fLastIncomingPacket = null;
			this.fLastOutgoingPacket = null;

			this.fActive = true;
			this.fReadIncomingDataThread = new ReadIncomingDataThread(this);
			this.fReadIncomingDataThread.start();
		} catch (SocketException socketException) {
			this.fActive = false;
			this.stop();
			throw socketException;
		}
	}

	public Async2UDPServer getUDPServer() {
		return this.fUDPServer;
	}

	/**
	 * Returns the name of this UDP Listener.
	 *
	 * @return the name of this UDP Listener.
	 */
	public String getName() {
		return this.fName;
	}

	/**
	 * Send data to udp client.
	 *
	 * @param packet is the datagram packet to be send.
	 */
	public void sendData(final DatagramPacket packet) {
		this.fLastOutgoingPacket = new OutgoingUDPServerPacket(this, packet, this.fLastOutgoingPacket);
	}

	public InetAddress getIPAddress() {
		return this.fIPAddress;
	}

	/**
	 * Returns the UDP port of this listener.
	 *
	 * @return the UDP port of this listener.
	 */
	public int getPort() {
		return this.fPort;
	}

	public int getReadBufferSize() {
		return this.fReadBufferSize;
	}

	public long getBytesIn() {
		return this.fBytesIn;
	}

	public long getBytesOut() {
		return this.fBytesOut;
	}

	public boolean isActive() {
		return this.fActive;
	}

	class ReadIncomingDataThread extends Thread {
		private UDPListener fUDPListener;

		public ReadIncomingDataThread(final UDPListener UDPListener) {
			this.fUDPListener = UDPListener;
		}

		@Override
		public void run() {
			byte[] data;
			DatagramPacket receivedPacket;

			while (this.fUDPListener.isActive()) {
				try {
					data = new byte[this.fUDPListener.getReadBufferSize()];
					receivedPacket = new DatagramPacket(data, data.length);
					this.fUDPListener.fSocket.receive(receivedPacket);
					this.fUDPListener.fBytesIn += receivedPacket.getLength();
					this.fUDPListener.fLastIncomingPacket = new IncomingUDPServerPacket(this.fUDPListener, receivedPacket,
							this.fUDPListener.fLastIncomingPacket);
				} catch (Exception e) {
					JVM.errorln(e);
				}
			}
		}
	}
}
